
% Created 2011-10-27 do 16:51
\documentclass[a4paper, 11pt]{article}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{fixltx2e}
\usepackage{graphicx}
\usepackage{longtable}
\usepackage{float}
\usepackage{wrapfig}
\usepackage{soul}
\usepackage{textcomp}
\usepackage{tikz}
\usepackage{listings}
\usepackage{color}
\usetikzlibrary{positioning,shapes,shadows,arrows}
%\usepackage{marvosym}
%\usepackage{wasysym}
%\usepackage{latexsym}
\usepackage{amssymb}
\usepackage{mathtools}
\usepackage{hyperref}
\usepackage{placeins}
\usepackage{pict2e}
\usepackage{subfig}
\usepackage{appendix}
%\usepackage{cmbright}
\usepackage[dutch]{babel}
\usepackage[a4paper,margin=2.5cm]{geometry}
 \hyphenpenalty=5000
\tolerance=1000
\providecommand{\alert}[1]{\textbf{#1}}
\newfloat{MATLAB code}{h}{}
\usepackage{tikz,pgfplots}
\newcommand{\code}[1]{\textbf{\texttt{#1}}}
\pgfplotsset{compat=newest}
\pgfplotsset{plot coordinates/math parser=false}

\title{Technisch Wetenschappelijke Software: Project Eindverslag}
\author{Roel Matthysen \\ 1e Master Wiskundige Ingenieurstechnieken}
\date{\today}

\newlength\figureheight
\newlength\figurewidth
\setlength\figureheight{2cm}
\setlength\figurewidth{12cm}


\begin{document}
\maketitle
\tableofcontents
\clearpage

\section*{Inleiding}
Dit project bestaat uit het ontwerpen, implementeren en testen van een bibliotheek om de exponenti\"ele van een matrix te berekenen. Bij het uitvoeren van deze opdracht worden principes aangeleerd in het vak Technisch Wetenschappelijke Software in de praktijkt toegepast. Na een korte theoretische achtergrond bij de gebruikte algoritmes wordt in twee secties uitgelegd welke de gevolgde stappen zijn voor de verschillende delen. Daarna worden enkele designbeslissingen toegelicht in sectie \ref{sec:designbeslissingen}. In sectie \ref{sec:extrasoftware} wordt beschreven welke extra software en extra bibliotheken juist gebruikt zijn. In sectie \ref{sec:resultaten} wordt dan de performantie van de bibliotheek beschreven, waarna enkele conclusies gemaakt worden in sectie \ref{sec:besluit}.
\section{Theoretische Achtergrond}
\subsection{Theorie deel 1}
De module geschreven voor deze opdracht rekent de exponenti\"ele functie uit met als input een gegeven matrix A. De Taylorreeksontwikkeling van $e^x$ is $\sum_{j=0}^{\infty}\frac{x^j}{j!}$, en voor een matrix A geeft dit
\begin{equation}
e^A=\sum_{j=0}^{\infty}\frac{A^j}{j!}=I+A+\frac{AA}{2}+\frac{AAA}{6}+\cdots
\label{eqn:taylorexp}
\end{equation}
Uitgaande van deze reeksontwikkeling wordt $e^A$ berekend met behulp van de eigenwaardenontbinding van A
\begin{equation}
A=V \Lambda V^{-1}\mbox{\hspace{12pt}met\hspace{12pt}}\Lambda=diag(\lambda_1,\lambda_2,...,\lambda_n).
\label{eqn:eigdecomposition}
\end{equation}
We weten dat $V^{-1}V=I$, en invullen van \ref{eqn:eigdecomposition} in \ref{eqn:taylorexp} geeft dan
\begin{eqnarray*}
e^A&=&I+A+\frac{AA}{2}+\frac{AAA}{6}+\cdots \\
       &=&VV^{-1}+V \Lambda V^{-1}+\frac{V \Lambda V^{-1}V \Lambda V^{-1}}{2} + \frac{V \Lambda V^{-1}V \Lambda V^{-1}V \Lambda V^{-1}}{6}\cdots \\
       &=&V\left(I+\Lambda + \frac{\Lambda \Lambda}{2}+\frac{\Lambda \Lambda \Lambda}{6}\cdots\right)V^{-1} \\
       &=&Ve^{\Lambda}V^{-1}
\end{eqnarray*}
Voorts weten we ook dat $e^{\Lambda}$ gemakkelijk kan worden uitgerekend omdat
\begin{eqnarray*}
e^{\Lambda} &=& I+\Lambda + \frac{\Lambda \Lambda}{2}+\frac{\Lambda \Lambda \Lambda}{6}\cdots \\
		     &=& \begin{bmatrix} 1+\lambda_1+\frac{\lambda_1\lambda_1}{2}+... & & \cdots & 0 \\
		     					 & 1+\lambda_2+\frac{\lambda_2\lambda_2}{2}+... & & \vdots \\
							\vdots & & \ddots & \\
							0 & \cdots & & 1+\lambda_n+\frac{\lambda_n\lambda_n}{2}+... \end{bmatrix} \\
		     &=& diag(\sum_{j=0}^{\infty}\frac{\lambda_1^j}{j!},\sum_{j=0}^{\infty}\frac{\lambda_2^j}{j!},...,\sum_{j=0}^{\infty}\frac{\lambda_n^j}{j!}) \\
		     &=& diag(e^{\lambda_1},e^{\lambda_2},..,e^{\lambda_n}) 
\end{eqnarray*}
\clearpage
\subsection{Theorie deel 2}
\label{sec:theoriedeel2}
In deel 2 wordt gevraagd om aangepaste versies van de algoritmes  te schrijven voor circulante matrices, die de speciale structuur van deze matrices uitbuiten. Een circulante matrix $C \in \mathbb{C}^{nxn}$ is volledig gekarakteriseerd door zijn eerste kolom $c \in \mathbb{C}^{nxn}$. \begin{equation}
C=circ(c):=\begin{bmatrix} c_0& c_{n-1}& c_{n-2}& \cdots& c_1 \\
c_1 & c_0 & c_{n-1} & \cdots & c_3 \\
c_2 & c_1 & c_0 & \cdots & c_3 \\
\vdots & \vdots & \vdots & \ddots & \vdots \\
c_{n-1} & c_{n-2} & c_{n-3} & \cdots & c_0 \end{bmatrix} 
\end{equation}
\newline
Het vermenigvuldigen van twee circulante matrixes, $A=circ(a)$ en $B=circ(b)$ is equivalent met het cyclisch convolueren van hun eerste kolommen om de eerste kolom van het resultaat te bekomen. Wanneer $a$ en $b$ eerst getransformeerd worden naar het Fourierdomein, komt deze convolutie neer op een elementsgewijze vermenigvuldiging.
\newline
\newline
De eigenwaardenontbinding van een circulante matrix is expliciet gekend:
\begin{equation}
C=F_n^{-1} DF_n,
\end{equation}
met $F_n$ de Fouriermatrix van orde $n$, en met op de diagonaal van $D$ de discrete Fouriertransformatie van $c$. De fouriertransformatie transformeert de eerste kolom $c$ van de circulante matrix dus in de eigenwaarden van $C$. Omgekeerd zal de inverse fouriertransformatie, mits correct genormaliseerd, de eigenwaarden van een circulante matrix terug transformeren in de eerste kolom van die matrix.
\newline
\newline
De algoritmes kunnen dan op de volgende manier aangepast worden:
\begin{description}
\item[Taylor] De vermenigvuldiging van de matrix wordt vervangen door zijn circulante equivalent. De taylorexpansie wordt volledig uitgerekend in het Fourierdomein, en op het einde wordt het resultaat teruggetransformeerd. Concreet worden de volgende stappen gevolgd:
\begin{itemize}
\item Bepaal $\tilde{a}$, de Fouriergetransformeerde van de eerste kolom van A.
\item Bereken de matrixexponenti\"ele van A met behulp van de taylorexpansie. De matrixvermenigvuldigingen worden hier vervangen door elementsgewijze vermenigvuldigen van $\tilde{a}$. Dit komt neer op het elementsgewijs berekenen van $e^{\tilde{a}}$ met behulp van de taylorexpansie.
\item Bepaal de inverse Fouriertransformatie van de berekende $e^{\tilde{a}}$ om de eerste kolom van het resultaat terug te vinden
\end{itemize}
\item[EIG] De eigenwaardenontbinding komt neer op het Fouriertransformeren van de eerste kolom van de matrix $A$. Zoals in de versie voor volle matrices wordt nu de exponenti\"ele van elke eigenwaarde berekend. De matrixvermenigvuldigingen en het oplossen van het stelsel worden hier echter vervangen door de inverse Fouriertransformatie.
\begin{itemize}
\item Bepaal $\tilde{a}$, de Fouriergetransformeerde van de eerste kolom van A.
\item Bereken de matrixexponenti\"ele van A met behulp van een ingebouwd commando zoals \code{exp}.
\item Bepaal de inverse Fouriertransformatie van de berekende $e^{\tilde{a}}$ om de eerste kolom van het resultaat terug te vinden
\end{itemize}
\item[EIGQR] Dit algoritme is voor circulante matrices volledig equivalent aan \code{EIGQR}.
\end{description}
%%%STAPPEN
\input{stappen}
%%%DESIGN
\input{design}
\clearpage
%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%% EXTRA SOFTWARE EN BIBLIOTHEKEN
%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{Extra software en bibliotheken}
\label{sec:extrasoftware}
De gebruikte routines uit LAPACK zijn:
\begin{description}
\item[DGEEV] Gebruikt om eigenwaarden en eigenvectoren te berekenen. Deze methode werkte in mijn geval stukken beter dan ZGEEV.
\item[ZGESV] Gebruikt om het stelsel $e^AV=Ve^{\Lambda}$uit het algoritme \code{eig} op te lossen. Vooraleer deze routine toe te passen werden wel transposes genomen om overeen te komen met de conventie van de routine.
\item[ZGEQRF] Gebruikt om de QR-ontbinding van $V$ te berekenen in het algoritme \code{eigqr}.
\item[ZUNGQR] Gebruikt om de matrix $Q$ expliciet op te stellen.
\item[ZTRTRS] Gebruikt om het driehoeksstelsel $RY=Q^{*}$ op te lossen
\end{description}
De gebruikte routines uit FFTW zijn:
\begin{description}
\item[dfftw\_plan\_dft\_c2r\_1d] Stelt een plan of voor de voorwaartse Fast Fourier transform.
\item[dfftw\_execute\_dft\_c2r] Voert de voorwaartse Fouriertransformatie uit.
\item[dfftw\_plan\_dft\_r2c\_1d] Variant voor de inverse transformatie.
\item[dfftw\_execute\_dft\_r2c] Variant voor de inverse transformatie.
\item[dfftw\_destroy\_plan] Verwijdert het aangemaakte plan.
\end{description}
Er zijn ook enkele extra programma's uitgeprobeerd om het debuggen en optimaliseren te vergemakkelijken.
\begin{description}
\item[VALGRIND] is nuttig om het geheugengebruik in kaart te brengen. Bij een programma mag normaal gezien het aantal bytes bij 'definitely lost' en 'indirectly lost' niet groter dan 0 zijn. Deze waarde was echter niet gelijk aan 0, waardoor duidelijk werd dat sommige variabelen niet gedealloceerd werden. Ook werd een \code{OPEN} statement niet altijd vergezeld van een \code{CLOSE} statement, iets wat tegen de verwachting in ook geheugen lekte.
\item[GCOV] is nuttig om na te kijken welke lijnen code effectief uitgevoerd werden. Dit programma geeft weer hoeveel procent van de lijnen er is uitgevoerd, en genereert een file '\code{*.f95.gcov}'. In deze file staat een listing van de code, met bij elke regel een aanduiding hoeveel keer de regel is uitgevoerd. Dit was handig om na te gaan of de testen wel degelijk alle code in \code{methods} testten.
\item[GPROF] is een profiler, die laat zien welke functies het meeste tijd opeisen bij een uitvoering. De output van dit programma bood eigenlijk geen meerwaarde over de timings die gebruikt worden in \code{test}.
\end{description}
Tenslotte moet nog vermeld worden dat de module \code{time} niet zelf geschreven is, maar overgenomen uit oefenzitting 2.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%RESULTATEN
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\clearpage
\section{Resultaten}
\label{sec:resultaten}
\subsection{Tijdscomplexiteit}
Om de tijdscomplexiteit te bepalen, zijn de timings uit \code{test} gebruikt voor verschillende matrixgroottes. De aanpak uit Deel 1, waarbij \code{matrixexp} werd aangeroepen vanuit een shell-script, was omslachtig en rekende de overhead van \code{matrixexp} mee. De resultaten voor volle matrices zijn weergegeven in figuur \ref{fig:fulllog}. Uit deze figuur blijkt dat de complexiteit van de algoritmes ongeveer gelijk is, en ergens tussen 2 en 3 ligt:
\begin{equation}
O_{taylor}=    2.7466 , O_{eig} =  2.5511, O_{eigqr} =  2.6673
\end{equation}
Deze timings kunnen uiteraard vari\"eren per uitvoering, zijn afhankelijk van de ligging van de eigenwaarden, en zijn be\"invloed door de tolerantie bij het berekenen van de exponenti\"ele met de taylorbenadering. De resultaten geven echter een goed beeld van de complexiteit van de methodes.
\newline
\newline
In het circulante geval moeten slechts 2 algoritmes vergeleken worden, omdat \code{eigc} en \code{eigqrc} equivalent zijn. De resultaten zijn weergegeven in figuur \ref{fig:circlog}. Bij de groottes van de matrices is voor oplopende machten van 2 gekozen, omdat de factorisatie van $N$ invloed heeft op de complexiteit van de FFT. Uit de figuur blijkt dat de berekening van de exponenti\"ele met de taylorexpansie, het enige verschil tussen de methodes, de uitvoeringstijd van \code{taylorexpc} domineert. De methodes zijn allebei ongeveer $O(N)$ met berekende complexiteit
\begin{equation}
O_{taylorc}=1.2473, O_{eigc}=0.9157
\end{equation}
De FFTW is $O(N\log{N})$, dus de verwachting is dat ook \code{eigc} zich zo zal gedragen. Op de figuur zijn echter invloeden bij kleine matrixgroottes te zien, en in de heel korte methode \code{eigc} heeft de oproep van \code{exp} ook een grote invloed op de uitvoeringstijd. Indien de eerste twee metingen worden weggelaten in de berekening van de complexiteit is deze groter dan 1\begin{equation}
O_{eigc}=1.0057
\end{equation}
\begin{figure}[p]
\centering
\includegraphics{img/fulllog.eps}
\caption{De uitvoeringstijd van de algoritmes op een volle matrix, op logaritmische schaal. De zwarte stippellijnen geven een rechte met helling ($O(N^2)$) en helling ($O(N^3)$) aan.}
\label{fig:fulllog}
\end{figure}
\begin{figure}[p]
\centering
\includegraphics{img/circlog.eps}
\caption{De uitvoeringstijd van de algoritmes op een circulante matrix, op logaritmische schaal. De zwarte stippellijnen geven een rechte met helling $O(N)$ en een rechte met helling $O(N\log{N})$ aan.}
\label{fig:circlog}
\end{figure}
\subsection{Invloed van de norm}

Om de invloed van de norm na te gaan werden volle en circulante matrices geconstrueerd met $N=128$ en een norm lopende van 1 tot 256. In figuren \ref{fig:normfull} en \ref{fig:normcirc} worden de tijdsmetingen weergegeven.
\newline
\newline
Uit de figuren blijkt dat zowel de eigenwaardenontbinding als de fouriertransformatie qua uitvoeringstijd niet gevoelig zijn aan de norm. De berekening van de exponenti\"ele functie met de taylorexpansie daarentegen stijgt in beide gevallen wel snel met de norm. Dit is te verklaren omdat de taylorbenadering die gebruikt wordt voor $e^x$ is opgesteld rond 0. Hoe verder $x$ van 0 is, hoe trager de convergentie en hoe meer termen er nodig zijn. Een gelijkaardige redenering geldt ook voor de taylorbenadering van $e^A$, hoe groter de norm van $A$, hoe meer termen er nodig zijn in de benadering. 
\newline
\newline
Verder moet nog worden opgemerkt dat de norm geen merkbare invloed heeft op de fout van de algoritmes \code{eig} en \code{eigqr}. De fout op de algoritmes met taylorexpansie hangt wel sterk af van de gebruikte tolerantie, en het maximaal toegestane aantal iteraties, omdat het kan zijn dat de benadering simpelweg niet convergeert.
\begin{figure}[p]
\centering
\includegraphics{img/normfull.eps}
\caption{De uitvoeringstijd van de algoritmes op een volle matrix, op logaritmische schaal voor oplopende norm. De grootte van de invoermatrix is 128x128.}
\label{fig:normfull}
\end{figure}
\begin{figure}[p]
\centering
\includegraphics{img/normcirc.eps}
\caption{De uitvoeringstijd van de algoritmes op een circulante matrix, op logaritmische schaal voor oplopende norm. De lengte van de invoermatrix is 128.}
\label{fig:normcirc}
\end{figure}

\clearpage
\subsection{Geheugengebruik}
In Tabellen 2  en 3 wordt samengevat hoeveel van elke soort variabelen nodig zijn per algoritme, plus een totaal in aantal bytes voor een matrix van grootte N. Dit is na een optimalisatiestap, waarbij eerst met behulp van de waarschuwingen van \code{ifort} werd gekeken of er ongebruikte variabelen tussen stonden, en daarna werd gekeken of voor sommige tussenresultaten geen reeds gealloceerd geheugen gebruikt kon worden.
\newline
\newline
In het eindresultaat is vooral de dominante term van belang, die is voor \code{taylorexp} $24N^2$, voor \code{eig} $136N^2$ en voor \code{eigqr} $152N^2$. De methode met taylorreeksontwikkeling wint dus duidelijk op het vlak van geheugengebruik, en het algoritme \code{eig} doet het iets beter dan \code{eigqr} omdat de extra variabelen voor de QR ontbinding niet moeten worden bijgehouden.
\newline
\newline
In het circulante geval is de dominante term voor \code{taylorexpc} $64N$, en voor \code{eigc} $32N$. Het is duidelijk dat deze algoritmes veel goedkoper zijn in geheugengebruik dan deze voor volle matrices, en tussen de methodes onderling is \code{eigc} goedkoper omdat de variabelen voor de taylorexpansie niet moeten worden bijgehouden.
\begin{table}[htdp]
\begin{center}
\begin{tabular}{|rl|c|c|c|}
\hline
 & & Taylorexp & Eig & Eigqr \\
 \hline
 NxN & double complex 	& & 2+1* & 3+1*\\
 NxN & double real		& 3 & 2+3* & 2+3* \\
 Nx1 & double complex	& & 1+1* & 2+1* \\
 Nx1 & double real		& & 2* & 2* \\
 &Scratch space & & 1* & 1+1* \\
 Nx1 & integer			& & 1 & \\
 1x1 & integer			& 1 & 3+3* & 3+3* \\
 1x1 & logical			& 1 & 1+1* & 1+1* \\
 \hline
 totaal & bytes			& $24N^2+O(N)$ & $136N^2+O(N)$ & $152N^2+O(N)$ \\
 \hline
\end{tabular}
\label{tbl:rfout}
\caption{Lijst van het geheugengebruik van de verschillende algoritmes voor volle matrices. Om de vergelijking te vergemakkelijken wordt aangenomen dat de grootte van de scratch space $O(N)$ is. \\ *=Dit geheugen wordt gebruikt in de subroutine \code{zgeeveigen} om de eigenwaarden uit te rekenen.}
\end{center}
\end{table}%
\begin{table}[htdp]
\begin{center}
\begin{tabular}{|rl|c|c|c|}
\hline
 & & Taylorc & Eigc \\
 \hline
 Nx1 & double complex	& 3&  1\\
 Nx1 & double real		& 2&   2\\
 1x1 & integer & 1 & \\
 1x1 & logical & 1 & 1\\
 \hline
 totaal & bytes			& $64N+O(1)$ & $32N+O(1)$ \\
 \hline
\end{tabular}
\label{tbl:rfout}
\caption{Lijst van het geheugengebruik van de verschillende algoritmes voor circulante matrices.}
\end{center}
\end{table}%
\clearpage
\subsection{Nauwkeurigheid}
Om de nauwkeurigheid na te gaan, werden genormaliseerde testmatrices geconstrueerd van oplopende grootte. De fout ten opzichte van de uitkomst berekend in MATLAB via \code{expm} wordt dan getoond in figuren \ref{fig:fullerror} en \ref{fig:circerror}. 
\newline
\newline
Bij de volle matrices is de fout voor \code{Taylor} constant, wat logisch is gezien de tolerantie ingesteld is op de relatieve fout van het resultaat. In dit geval is de tolerantie op de maximale relatieve fout $10^{-10}$, wat duidelijk terugkomt in het resultaat, dat daar ongeveer een factor 5 onder blijft. Bij de algoritmes \code{eig} en \code{eigqr} daalt de nauwkeurigheid wel met het stijgen van de matrixgrootte.
\newline
\newline
In het circulante geval stijgt de fout lichtjes voor zowel \code{taylorexpc} als \code{eigc}
\begin{figure}[p]
\centering
\includegraphics{img/fullerror.eps}
\caption{De relatieve fout in functie van de matrixgrootte voor volle matrices.}
\label{fig:fullerror}
\end{figure}
\begin{figure}[p]
\centering
\includegraphics{img/circerror.eps}
\caption{De relatieve fout in functie van de matrixgrootte voor circulante matrices.}
\label{fig:circerror}
\end{figure}
\subsection{Invloed van de compiler en optimalisatievlaggen}
Er werd ook kort de invloed van de compilers en optimalisatievlaggen nagegaan. Hiervoor werd gekeken naar de uitvoering van de algoritmes voor een 256x256 matrix voor de drie geteste compilers. De gemiddelde waarden over 4 experimenten worden weergegeven in tabel 4. Uit de tabel blijkt dat de verschillen tussen de compilers hoog kunnen oplopen, maar de de optimalisatievlaggen relatief weinig invloed hebben. Bij \code{ifort} heeft de optimalisatievlag zelfs een negatieve invloed.
\newline
\newline
Dit is te verklaren doordat de grootste rekentijden in voorgecompileerde LAPACK-routines zitten. De optimalisaties hebben hier dus geen invloed op.
\begin{table}[htdp]
\begin{center}
\begin{tabular}{|l|c|c|c|}
\hline
 & Taylorexp & Eig & Eigqr \\
 \hline
 \textbf{gfortran} & 0.236& 0.456 & 0.528 \\
 \textbf{gfortran} -O3 & 0.232 & 0.452 &  0.524\\
 \textbf{g95} & 1.112 & 0.724&  1.044 \\
 \textbf{g95} -O3 & 1.088 & 0.716 & 1.036\\
\textbf{ifort} &0.384     &  0.332     &  0.404 \\
\textbf{ifort} -O3 & 1.392 & 0.376 &  0.632 \\
 \hline
\end{tabular}
\label{tbl:rfout}
\caption{De rekentijden voor een 256x256 volle matrix voor het programma gecompileerd met verschillende compilers en compilervlaggen.}
\end{center}
\end{table}%

\clearpage
\section{Besluit}
\label{sec:besluit}
\subsection*{Deel 1}
De moeilijkheid van deze opdracht lag niet in het implementeren van de algoritmes, maar in het goed nadenken welke types en welke methodes nodig waren. Het is jammer dat de onnauwkeurigheid van de methodes ZGEEV en ZGEQRF zoveel tijd gekost heeft, maar het zoeken naar de fout was wel zeer leerzaam en heeft zeker meer inzicht verschaft dan een onmiddellijk werkend programma. Het vele debuggen had ook als voordeel dat de geautomatiseerde testen hun doel konden bewijzen. \newline
\newline
Tijdsbesteding: Ongeveer 40 uur 

\subsection*{Deel 2}
Voor deel 2 kostte de implementatie van de algoritmes relatief weinig tijd. De grote moeite van dit deel zat in het herdenken en verbeteren van Deel 1, en het uitbreiden van de testen. Met het testen op grotere matrices kwamen problemen aan het licht die zich niet voordeden bij de testen van Deel 1, zoals het probleem met de scratch space en het inlezen van de matrices. Uiteindelijk was het een leerzaam project, waarbij vooral werd geleerd wat voor fouten er kunnen optreden bij het schrijven van numerieke software, en hoe deze kunnen vermeden worden.
\newline
\newline
Tijdsbesteding: Ongeveer 25 uur
\clearpage
\appendix
\section{Klassediagramma}
\label{sec:klassediagramma}
\begin{figure}[htp]
\input{classdiagram}
\caption{Klassediagramma voor de uiteindelijke bibliotheek. De module \code{time}, die enkel gebruikt wordt door \code{test} is hier achterwege gelaten.}
\end{figure}
\end{document}

%%% Local Variables: 
%%% mode: latex
%%% TeX-master: t
%%% End: